package at.markushi.ui;

import at.markushi.ui.action.Action;
import at.markushi.ui.action.BackAction;
import at.markushi.ui.action.CloseAction;
import at.markushi.ui.action.DrawerAction;
import at.markushi.ui.action.LineSegment;
import at.markushi.ui.action.PlusAction;
import at.markushi.ui.util.UiHelper;
import ohos.agp.animation.AnimatorValue;
import ohos.agp.components.AttrHelper;
import ohos.agp.components.AttrSet;
import ohos.agp.components.Component;
import ohos.agp.render.Canvas;
import ohos.agp.render.Paint;
import ohos.agp.render.Path;
import ohos.agp.utils.Color;
import ohos.app.Context;


import static at.markushi.ui.AttrSetString.ACTIONVIEW_AV_ACTION;
import static at.markushi.ui.AttrSetString.ACTIONVIEW_AV_COLOR;
import static ohos.agp.utils.Color.BLACK;
import static ohos.agp.utils.Color.WHITE;

/**
 * ActionView allows you to dynamically display action and automatically animate from one action to another.
 * <p/>
 * ActionView can be included in your layout xml quite easy:
 *
 * In order to start a transition to another {@link Action} call {@link #setAction(Action)}
 *
 * @see BackAction
 * @see CloseAction
 * @see DrawerAction
 * @see PlusAction
 */
public class ActionView extends Component implements Component.LayoutRefreshedListener{
	public static final int ROTATE_CLOCKWISE = 0;
	public static final int ROTATE_COUNTER_CLOCKWISE = 1;
	private static final int STROKE_SIZE_DIP = 2;

	private Action oldAction;
	private Action currentAction;
	private Action animatedAction;

	private float animationProgress;
	private float scale;
	private int padding;
	private Path path;
	private Paint paint;

	private float centerX;
	private float centerY;
	private boolean ready = false;
	private boolean animateWhenReady = false;
	private int rotation = ROTATE_CLOCKWISE;
	private long animationDuration;
	private int size;


	public ActionView(Context context) {
		this(context, null);
	}

	public ActionView(Context context, AttrSet attrSet) {
		this(context, attrSet, 0);

		animationDuration = 340;
		animatedAction = new Action(new float[Action.ACTION_SIZE], null);

		final float strokeSize = STROKE_SIZE_DIP;
		path = new Path();
		paint = new Paint();
		paint.setColor(BLACK);
		paint.setStrokeWidth(strokeSize);
		paint.setStyle(Paint.Style.STROKE_STYLE);

		if (attrSet == null) {
			return;
		}
		final Color color = attrSet.getAttr(ACTIONVIEW_AV_COLOR).isPresent() ? attrSet.getAttr(ACTIONVIEW_AV_COLOR).get().getColorValue() : WHITE;
		final int actionId = attrSet.getAttr(ACTIONVIEW_AV_ACTION).isPresent() ? attrSet.getAttr(ACTIONVIEW_AV_ACTION).get().getIntegerValue() : 0;

		paint.setColor(color);
		final Action action = getActionFromEnum(actionId);
		setAction(action);

		// 添加绘制任务
		addDrawTask(this::onDraw);
		onRefreshed(this);
	}

	public ActionView(Context context, AttrSet attrSet, String styleName) {
		super(context, attrSet, styleName);

	}

	public ActionView(Context context, AttrSet attrSet, int resId) {
		super(context, attrSet, resId);
	}


	private void onDraw(Component component, Canvas canvas) {
		if (currentAction == null) {
			return;
		}

		if (oldAction == null) {
			updatePath(currentAction);
		} else {
			final float inverseProgress = 1f - animationProgress;
			final float[] current = currentAction.getLineData();
			final float[] old = oldAction.getLineData();
			final float[] animated = animatedAction.getLineData();
			for (int i = 0; i < animated.length; i++) {
				animated[i] = current[i] * animationProgress + old[i] * inverseProgress;
			}

			updatePath(animatedAction);
		}

		canvas.rotate((rotation == ROTATE_CLOCKWISE ? 180f : -180f) * animationProgress, centerX, centerY);
		canvas.drawPath(path, paint);
	}

	@SuppressWarnings("UnusedDeclaration")
	public float getAnimationProgress() {
		return animationProgress;
	}

	@SuppressWarnings("UnusedDeclaration")
	public void setAnimationProgress(float animationProgress) {
		this.animationProgress = animationProgress;
		UiHelper.postInvalidateOnAnimation(this);
	}

	/**
	 * Set the color used for drawing an {@link Action}.
	 *
	 * @param color
	 */
	public void setColor(final Color color) {
		paint.setColor(color);
		UiHelper.postInvalidateOnAnimation(this);
	}

	/**
	 * 得到currentAction
	 *
	 * @return the current action
	 */





	public Action getAction() {
		return currentAction;
	}

	/**
	 * Sets the new action. If an action was set before a transition will be started.
	 *
	 * @param action
	 * @see #setAction(Action, boolean)
	 * @see #setAction(Action, boolean, int)
	 * @see #setAction(Action, Action, int, long)
	 */
	public void setAction(final Action action) {
		setAction(action, true, ROTATE_CLOCKWISE);
	}

	/**
	 * Sets the new action. If an action was set before a transition will be started.
	 *
	 * @param action
	 * @param rotation Can be either {@link #ROTATE_CLOCKWISE} or {@link #ROTATE_COUNTER_CLOCKWISE}.
	 */
	public void setAction(final Action action, final int rotation) {
		setAction(action, true, rotation);
	}

	/**
	 * Sets the new action.
	 *
	 * @param action
	 * @param animate If a prior action was set and {@code true} a transition will be started, otherwise not.
	 */
	public void setAction(final Action action, final boolean animate) {
		setAction(action, animate, ROTATE_CLOCKWISE);
	}

	/**
	 * Sets a new action transition.
	 *
	 * @param fromAction The initial action.
	 * @param toAction   The target action.
	 * @param rotation   The rotation direction used for the transition {@link #ROTATE_CLOCKWISE} or {@link #ROTATE_COUNTER_CLOCKWISE}.
	 * @param delay      The delay in ms before the transition is started.
	 */
	public void setAction(final Action fromAction, final Action toAction, final int rotation, long delay) {
		setAction(fromAction, false, ROTATE_CLOCKWISE);
		mContext.getMainTaskDispatcher().delayDispatch(() -> {
			if (!isBoundToWindow()) {
				return;
			}
			setAction(toAction, true, rotation);
		}, delay);
	}


	/**
	 * Set the animation duration used for Action transitions
	 *
	 * @param animationDuration the duration in ms
	 */
	public void setAnimationDuration(long animationDuration) {
		this.animationDuration = animationDuration;
	}

	private void setAction(Action action, boolean animate, int rotation) {
		if (action == null) {
			return;
		}

		this.rotation = rotation;
		if (currentAction == null) {
			currentAction = action;
			currentAction.flipHorizontally();
			animationProgress = 1f;
			UiHelper.postInvalidateOnAnimation(this);
			return;
		}

		if (currentAction.getClass().equals(action.getClass())) {
			return;
		}

		oldAction = currentAction;
		currentAction = action;

		if (animate) {
			animationProgress = 0f;
			if (ready) {
				startAnimation();
			} else {
				animateWhenReady = true;
			}
		} else {
			animationProgress = 1f;
			UiHelper.postInvalidateOnAnimation(this);
		}
	}

	private void updatePath(Action action) {
		path.reset();

		final float[] data = action.getLineData();
		// Once we're near the end of the animation we use the action segments to draw linked lines
		if (animationProgress > 0.95f && !action.getLineSegments().isEmpty()) {
			for (LineSegment s : action.getLineSegments()) {
				path.moveTo(data[s.getStartIdx() + 0], data[s.getStartIdx() + 1]);
				path.lineTo(data[s.getStartIdx() + 2], data[s.getStartIdx() + 3]);
				for (int i = 1; i < s.indexes.length; i++) {
					path.lineTo(data[s.indexes[i] + 0], data[s.indexes[i] + 1]);
					path.lineTo(data[s.indexes[i] + 2], data[s.indexes[i] + 3]);
				}
			}
		} else {
			for (int i = 0; i < data.length; i += 4) {
				path.moveTo(data[i + 0], data[i + 1]);
				path.lineTo(data[i + 2], data[i + 3]);
			}
		}
	}

	private void transformActions() {
		if (currentAction != null && !currentAction.isTransformed()) {
			currentAction.transform(padding, padding, scale, size);
		}

		if (oldAction != null && !oldAction.isTransformed()) {
			oldAction.transform(padding, padding, scale, size);
		}
	}

	private void startAnimation() {
		oldAction.flipHorizontally();
		currentAction.flipHorizontally();

		transformActions();

		animatedAction.setLineSegments(currentAction.getLineSegments());
		AnimatorValue animator = new AnimatorValue();
		animator.setValueUpdateListener(new AnimatorValue.ValueUpdateListener() {
			@Override
			public void onUpdate(AnimatorValue animatorValue, float v) {
				ActionView.this.setAnimationProgress(v);
			}
		});
		animator.setDuration(animationDuration);
		animator.start();
	}

	private Action getActionFromEnum(int id) {
		switch (id) {
		case 0:
			return new DrawerAction();
		case 1:
			return new BackAction();
		case 2:
			return new CloseAction();
		case 3:
			return new PlusAction();
		}

		return null;
	}

	@Override
	public void onRefreshed(Component component) {
		this.centerX = component.getWidth() / 2;
		this.centerY = component.getHeight() / 2;
		padding = getPaddingLeft();
		size = Math.min(component.getWidth(), component.getHeight());
		scale = Math.min(component.getWidth(), component.getHeight()) - (2 * padding);
		ready = true;
		transformActions();

		if (animateWhenReady) {
			animateWhenReady = false;
			startAnimation();
		}
	}
}
